package com.lifesting.tool.j2as3.visitor;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Set;
import java.util.Stack;

import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration;
import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.EnumConstantDeclaration;
import org.eclipse.jdt.core.dom.EnumDeclaration;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.Initializer;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.TypeParameter;

import com.lifesting.tool.j2as3.action.NodeWriter;
import com.lifesting.tool.j2as3.action.Util;
import com.lifesting.tool.j2as3.action.NodeWriter.Writer;
import com.lifesting.tool.j2as3.visitor.OverLoadMethodVisitor.OverloadMethodDescriptor;
import com.lifesting.tool.j2as3.visitor.SubTypeVisitor.SubTypeDescriptor;

public class BodyVisitor extends BaseVisitor {
	protected CompilationUnit unit;
	
	protected OverloadMethodDescriptor[] overloadConstructor;
	protected Map<String, SubTypeDescriptor> subTypeDescriptors;
	protected Set<String> overrideMethods;
	protected Set<String> needSetPointer;
	protected Stack<ASTNode> typeStack = new Stack<ASTNode>();
	protected HashMap<String,HashSet<String>> resolvedSignature;
	protected boolean enterConstructorMethod =false;
	public static final String ARGUMENTS="arguments";
	public static final String HIDE = "hide.definition";
	public BodyVisitor(NodeWriter nodeWriter) {
		super(nodeWriter);
	}
	public void setResolvedSignature(
			HashMap<String, HashSet<String>> resolvedSignature) {
		this.resolvedSignature = resolvedSignature;
	}
	public void setOverloadConstructor(
			OverloadMethodDescriptor[] overloadMethodDescriptors) {
		this.overloadConstructor = overloadMethodDescriptors;
	}
	public void setSubTypeDescriptors(
			Map<String, SubTypeDescriptor> subTypeDescriptors) {
		this.subTypeDescriptors = subTypeDescriptors;
	}
	public void setOverrideMethods(Set<String> overrideMethod) {
		this.overrideMethods = overrideMethod;
	}
	public void setNeedSetPointer(Set<String> needSetPointer) {
		this.needSetPointer = needSetPointer;
	}
	@Override
	public boolean visit(CompilationUnit node) {
		unit = node;
		return true;
	}
	@Override
	public void endVisit(CompilationUnit node) {
		IPath path = makePath();
		o(node).println("}");
		String buffer = o(node).getBuffer();
		try {
			peristent.save(path, buffer);
		} catch (Exception e) {
			logger.log(e);
		}
		unit = null;
	}
	protected String getUnitName(){
		String unitName = unit.getJavaElement().getElementName();
		return unitName.substring(0,unitName.lastIndexOf('.'));
	}
	@Override
	protected void reset() {
		super.reset();
		typeStack.clear();
	}
	private IPath makePath() {
		String package_path = packageName.replace('.', '/');
		IPath whole_path = new Path(package_path).append(unit.getJavaElement().getElementName());
		whole_path = whole_path.removeFileExtension().addFileExtension("as");
		return whole_path;
	}
	@Override
	public boolean visit(TypeDeclaration node) {
		String type_name = node.getName().getIdentifier();
		Util.addModifierProperty(node);
		Util.setName(node, type_name);
		recordParameterTypeName(node);
		
		typeStack.push(node);
		if(isNested()){
			saveScene(node);
			Util.removeInterModifier(node);
		}
		Util.setQn(node, stackQualifiedName());
		Writer writer = o(node);
		printIndents(writer);
		visitChildren(node.modifiers());
		if (node.isInterface()) {
			writer.print("interface ");
			//SubType name
			node.getName().accept(this);
			writer.print(" ");
			List interfaces = node.superInterfaceTypes();
			if (interfaces.size() > 0) {
				writer.print(" extends ");
				rightRecursiveVisit(interfaces, writer);
			}
			writer.println();
			printIndents(writer);
			writer.println("{");
			visitChildren(node.bodyDeclarations());
		} else {
			writer.print("class ");
			node.getName().accept(this);
			writer.print(" ");
			Type super_type = node.getSuperclassType();
			if (super_type != null) {
				writer.print(" extends ");
				super_type.accept(this);
			}
			List interfaces = node.superInterfaceTypes();
			if (interfaces.size() > 0) {
				writer.print(" implements ");
				rightRecursiveVisit(interfaces, writer);
			}
			writer.println();
			printIndents(writer);
			writer.println("{");
			if (needSetPointer.contains(Util.getQn(node))){
				printIndents(writer);
				writer.printTab(1);
				writer.println("var "+POINTER_KEY+":"+Util.getName(typeStack.get(typeStack.size()-2))+";");
			}
			visitChildren(node.bodyDeclarations());
		}
		return false;
	}
	private void recordParameterTypeName(TypeDeclaration node) {
		List pts = node.typeParameters();
		for (Object o : pts){
			parameterTypes.add(((TypeParameter)o).getName().getIdentifier());
		}
	}
	public boolean isNested(){
		return typeStack.size() > 1;
	}
	@Override
	public void endVisit(TypeDeclaration node) {
		Writer writer = o(node);
		
		printIndents(writer);
		writer.println("}");
		if(isNested()){
			saveSubclass(node);
			restoreScene(node);
		}
		typeStack.pop();
		parameterTypes.clear();
	}
	private void saveSubclass(TypeDeclaration node) {
		Writer writer = o(node);
		writer.headPrint(nodeWriter.getImports());
		writer.headPrint(nodeWriter.getPackage());
		writer.println("}"); // package closure;
		String type_buffer = writer.getBuffer();
		IPath target_file_path = makePath(stackQualifiedName());
		try {
			peristent.save(target_file_path, type_buffer);
		} catch (Exception e) {
			logger.log(e);
		}finally{
			writer.reset();
		}
		
	}
	protected String stackQualifiedName(){
		StringBuffer sb = new StringBuffer();
		sb.append(packageName);
		for (int i =0; i < typeStack.size(); i++){
			sb.append('.');
			sb.append(Util.getName(typeStack.get(i)));
		}
		return sb.toString();
	}
	private IPath makePath(String name) {
		String package_path = packageName.replace('.', '/');
		SubTypeDescriptor desc = subTypeDescriptors.get(name);
		IPath whole_path = new Path(package_path).append(desc.realTypeName).addFileExtension("as");
		return whole_path;
	}
	@Override
	public boolean visit(AnnotationTypeDeclaration node) {
		return false;
	}
	@Override
	public boolean visit(AnnotationTypeMemberDeclaration node) {
		return false;
	}
	@Override
	public boolean visit(EnumDeclaration node) {
		return false;
	}
	@Override
	public boolean visit(EnumConstantDeclaration node) {
		return false;
	}
	@Override
	public void endVisit(FieldDeclaration node) {
		Writer writer = o(node);
		writer.println(";");
	}
	@Override
	public void endVisit(Initializer node) {
		super.endVisit(node);
	}
	@Override
	public void endVisit(MethodDeclaration node) {
		Writer writer = o(node);
		enterConstructorMethod = false;
	}
	@Override
	public boolean visit(FieldDeclaration node) {
		Writer writer = o(node);
		printIndents(writer);
		removeFinalModifier(node);
		visitChildren(node.modifiers());
		writer.print("var ");
		node.setProperty(TYPE_KEY, node.getType());
		rightRecursiveVisit(node.fragments(), writer);
		return false;
	}
	private void removeFinalModifier(FieldDeclaration node) {
		List modifiers = node.modifiers();
		for (Object m : modifiers){
			Modifier modifier = (Modifier) m;
			if (modifier.isFinal()){
				modifier.setProperty(HIDE,Boolean.TRUE);
				break;
			}
		}
	}
	@Override
	public boolean visit(Initializer node) {
		visitChildren(node.modifiers());
		o(node).println();
		node.getBody().accept(this);
		return false;
	}
	@Override
	public boolean visit(MethodDeclaration node) {
		Writer writer = o(node);
		
		
		printIndents(writer);
		IMethodBinding imb = node.resolveBinding();
		if (!Util.isInterface(node.getParent()) && imb != null) {

			if (overrideMethods.contains(imb.getKey()))
				writer.print("override ");
			else {
				IMethod method = (IMethod) imb.getJavaElement();
				if (Util.canTrackMethod(node.getParent())
						&& foundMethodInSuperior(
								(AnonymousClassDeclaration) node.getParent(),
								Util.makeOverrideSignatures(method))) {
					writer.print("override ");
					// System.out.println("WOW!!!!");
				}
			}
		}
		visitChildren(node.modifiers());
		writer.print("function ");
		node.getName().accept(this);
		writer.print("(");
		rightRecursiveVisit(node.parameters(), writer);
		writer.print(")");
		if (!node.isConstructor()) {
			writer.print(":");
			node.getReturnType2().accept(this);
		}
		if (node.getBody() == null){
			if(Util.isAbstract(node)){
				writer.println();
				printIndents(writer);
				writer.println("{");
				printIndents(writer);
				writer.printTab(1).println(Util.getDefaultValue(node.getReturnType2()));
				printIndents(writer);
				writer.println("}");
			}else{
				writer.println(";");
			}
		}else{
			writer.println();
			node.getBody().accept(this);
		}
		return false;
	}

	/*private int queryIndex(MethodDeclaration node) {
		ConstructorDescriptor xxd = (ConstructorDescriptor) node.getParent().getProperty(Util.CONSTRUCTOR_DESCRIPTOR);
		List parameters = node.parameters();
		String[] type_str= new String[parameters.size()]; 
		for (int i = 0; i < parameters.size(); i++){
			SingleVariableDeclaration svd = (SingleVariableDeclaration) parameters.get(i);
			type_str[i] = Util.formatType(svd.getType());
		}
		return xxd.queryIndex(type_str);
	}*/
	private boolean foundMethodInSuperior(AnonymousClassDeclaration node, String methodSignature) {
		String[] anony_super_type = (String[]) node.getProperty(TypeRelatioShipVisitor.SUPER_TYPES);
		for (int i = 0; i < anony_super_type.length ; i++){
			HashSet<String> all_ss = resolvedSignature.get(anony_super_type[i]);
			if (all_ss != null && all_ss.contains(methodSignature))
				return true;
		}
		return false;
	}
	@Override
	public boolean visit(AnonymousClassDeclaration node) {
		typeStack.push(node);
		
		saveScene(node);
		Writer w = o(node);
		
		w.print(getNodeWriter().getPackage());
		w.print(getNodeWriter().getImports());
		printIndents(w);
		w.print("class "+Util.getName(node));
		ITypeBinding super_type_binding = ((Type)node.getProperty(TYPE_KEY)).resolveBinding();
		String super_type_name = super_type_binding.getName();
		boolean parent_pointer = false;
		if (super_type_binding.isInterface()){
			w.print(" implements ");
			parent_pointer = true;
			node.setProperty(Util.IS_INTERFACE, Boolean.TRUE);
		}else{
			Util.setAnonTrackMethod(node);
			w.print(" extends ");
			node.setProperty(TypeRelatioShipVisitor.SUPER_TYPES, buildInherient(super_type_binding));
			String super_qn = super_type_binding.getQualifiedName();
			if (needSetPointer.contains(super_qn))
				parent_pointer = true;
		}
		w.println(super_type_name);
		printIndents(w);
		w.println("{");
		if (parent_pointer){
			printIndents(w);
			w.printTab(1);
			w.println("var "+POINTER_KEY+":"+Util.getName(typeStack.get(typeStack.size()-2))+";");
		}
		return true;
	}
	@Override
	public void endVisit(AnonymousClassDeclaration node) {
		Writer w = o(node);
		printIndents(w);
		w.println("}");
		w.println("}");
		IPath path = makeAnonyPath(Util.getName(node));
		try {
			peristent.save(path, w.getBuffer());
		} catch (Exception e) {
			logger.log(e);
		}finally{
			w.reset();
		}
		restoreScene(node);
		typeStack.pop();
		
	}
	private IPath makeAnonyPath(String name) {
		String package_path = packageName.replace('.', '/');
		return new Path(package_path).append(name).addFileExtension("as");
	}
	private String[] buildInherient(ITypeBinding binding) {
		List<String> super_types = new ArrayList<String>();
		while (binding != null){
			String qn = binding.getQualifiedName();
			super_types.add(qn);
			if (!resolvedSignature.containsKey(qn))
			{
//				System.out.println(qn);
				collectOverridableMethod(binding);
			}
			if (!containType(qn)) break;
			binding = binding.getSuperclass();
		}
		return super_types.toArray(new String[super_types.size()]);
	}
	private void collectOverridableMethod(ITypeBinding binding) {
		IType javaelement = (IType) binding.getJavaElement();
		if (javaelement == null) return;
		HashSet<String> signs = new HashSet<String>();
		try {
			IMethod[] methods = javaelement.getMethods();
			for (IMethod m : methods){
				if (Util.overridable(m)){
					signs.add(Util.makeOverrideSignatures(m));
				}
			}
			resolvedSignature.put(binding.getQualifiedName(), signs);
		} catch (JavaModelException e) {
			e.printStackTrace();
		}
		
	}
	protected boolean containType(String p_name) {
		if (p_name == null) return false;
		for (String s : srcPackages) {
			if (p_name.startsWith(s)) {
				return true;
			}
		}
		return false;
	}

}
